package io.gitee.zhangbinhub.admin.gateway.repo

import com.fasterxml.jackson.databind.ObjectMapper
import io.gitee.zhangbinhub.acp.boot.log.LogAdapter
import io.gitee.zhangbinhub.admin.common.constant.RouteConstant
import jakarta.annotation.PostConstruct
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.cloud.gateway.event.RefreshRoutesEvent
import org.springframework.cloud.gateway.route.RouteDefinition
import org.springframework.cloud.gateway.route.RouteDefinitionRepository
import org.springframework.cloud.gateway.support.NotFoundException
import org.springframework.context.ApplicationEventPublisher
import org.springframework.data.redis.core.StringRedisTemplate
import org.springframework.stereotype.Component
import org.springframework.util.ObjectUtils
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono
import java.util.*

/**
 * @author zhang by 17/03/2019
 * @since JDK 11
 */
@Component
class RouteRedisRepository @Autowired constructor(
    private val logAdapter: LogAdapter,
    private val stringRedisTemplate: StringRedisTemplate,
    private val objectMapper: ObjectMapper,
    private val publisher: ApplicationEventPublisher
) : RouteDefinitionRepository {
    private val routes = Collections.synchronizedMap(LinkedHashMap<String, RouteDefinition>())

    @PostConstruct
    fun loadRouteDefinitions() {
        synchronized(this) {
            routes.clear()
            val values: MutableList<RouteDefinition> = mutableListOf()
            try {
                stringRedisTemplate.opsForList().range(RouteConstant.ROUTES_DEFINITION_KEY, 0, -1)?.let {
                    for (route in it) {
                        values.add(objectMapper.readValue(route, RouteDefinition::class.java))
                    }
                }
            } catch (e: Exception) {
                logAdapter.error(e.message, e)
            }
            logAdapter.info("redis 中路由定义条数： {}， {}", values.size, values)
            values.forEach { routeDefinition ->
                routes[routeDefinition.id] = routeDefinition
            }
        }
    }

    fun refreshRoute() {
        loadRouteDefinitions()
        publisher.publishEvent(RefreshRoutesEvent(this))
    }

    override fun getRouteDefinitions(): Flux<RouteDefinition> {
        return Flux.fromIterable(LinkedHashMap(routes).values)
    }

    override fun save(route: Mono<RouteDefinition>): Mono<Void> = route.flatMap { routeDefinition ->
        if (ObjectUtils.isEmpty(routeDefinition.id)) {
            return@flatMap Mono.error<Void>(IllegalArgumentException("id may not be empty"))
        }
        routes[routeDefinition.id] = routeDefinition
        Mono.empty()
    }

    override fun delete(routeId: Mono<String>): Mono<Void> = routeId.flatMap { id: String ->
        if (routes.containsKey(id)) {
            routes.remove(id)
            return@flatMap Mono.empty()
        }
        Mono.error(NotFoundException("RouteDefinition not found: $routeId"))
    }
}
